package xj.graph2d.shapes;

import static xj.graph2d.DrawShapeConstants.PointInBounds;
import static xj.graph2d.DrawShapeConstants.PointOnPolyShapeEdge;
import static xj.graph2d.DrawShapeConstants.PointOnPolyShapeVertex;
import static xj.graph2d.DrawShapeConstants.PointOutside;
import static xj.graph2d.DrawShapeConstants.d;

import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.geom.GeneralPath;
import java.awt.geom.Line2D;
import java.awt.geom.Path2D;
import java.awt.geom.Point2D;
import java.util.HashMap;
import java.util.Map;

import xj.graph2d.DrawShape;
import xj.graph2d.DrawShapeBase;
import xj.graph2d.GraphNodeBase;
import xj.graph2d.util.DrawingUtilities;
import xj.util.gui.Point2DFloat;

@SuppressWarnings("serial")
public class OrthogonalLineShape 
  extends PolyLineShape {

  public OrthogonalLineShape() {
    this(true, false);
  }

  public OrthogonalLineShape(float x1, float y1) {
    this(true, x1, y1, false);
  }

  public OrthogonalLineShape(boolean createComp) {
    this(true, createComp);
  }

  public OrthogonalLineShape(boolean startVertical, float x1, float y1) {
    this(startVertical, x1, y1, false);
  }

  public OrthogonalLineShape(boolean startVertical, boolean createComp) {
    super(createComp);
    this.startVertical = startVertical;
  }

  public OrthogonalLineShape(boolean startVertical, 
			     float x1, float y1,
			     boolean createComp) {
    super(x1, y1, createComp);
    this.startVertical = startVertical;
  }

  @Override
  public DrawShape makeInstance() {
    return new OrthogonalLineShape(startVertical, comp != null);
  }

  @Override
  public void copyTo(DrawShapeBase s) 
    throws CloneNotSupportedException {
    if (s != null) {
      super.copyTo(s);
      if (s instanceof OrthogonalLineShape) {
	OrthogonalLineShape l = (OrthogonalLineShape) s;
	l.startVertical = startVertical;
	l.roundCorner = roundCorner;
	l.arcHeight = arcHeight;
	l.arcWidth = arcWidth;
      }
    }
  }

  @Override
  public String getName() {
    return "Orthogonal line";
  }

  public boolean isStartVertical() {
    return startVertical;
  }

  public void setStartVertical(boolean startVertical) {
    this.startVertical = startVertical;
  }

  public boolean isEndVertical() {
    int n = adapter.getNumberOfPoints();
    return (n % 2 != 0) ? startVertical : !startVertical;
  }

  public boolean isFlippable() {
    return true;
  } 

  public void flipOrientation() {
    flipDirection();
  } 

  public void flipDirection() { 
    startVertical = !startVertical;
  }

  public boolean isRoundCorner() {
    return roundCorner;
  }

  public void setRoundCorner(boolean roundCorner) {
    this.roundCorner = roundCorner;
  }

  public float getArcWidth() {
    return arcWidth;
  }

  public float getArcHeight() {
    return arcHeight;
  }

  public void setArcWidth(float arcw) {
    arcWidth = arcw;
  }

  public void setArcHeight(float arch) {
    arcHeight = arch;
  }

  public void setArc(float arcw, float arch) {
    arcWidth = arcw;
    arcHeight = arch;
  }

  @Override
  public void draw(Graphics2D g2) {
    makePolyLine(g2);
  }

  protected Shape makePolyLine(Graphics2D g2) {
    if (head != null || tail != null) {
      setArrowPosition();
    }
    float x1, y1, x2, y2;
    int n = adapter.getNumberOfPoints() - 1;
    float x = adapter.getX();
    float y = adapter.getY();
    float prevx = x;
    float prevy = y;
    float curx, cury;

    float[] xp = new float[n + 2];
    float[] yp = new float[n + 2];

    xp[0] = x;
    yp[0] = y;
    boolean vertical = startVertical;
    for (int i = 0; i < n; i++) {
      Point2D p = adapter.getPoint(i);
      if (p != null) {
	curx = (float) p.getX() + x;
	cury = (float) p.getY() + y;
	x1 = prevx;
	y1 = prevy;
	x2 = curx;
	y2 = cury;
	if (i == 0 && tail != null && 
	    tail.getWidth() < Point2D.distance(x1, y1, x2, y2)) {
	  if (g2 != null) {
	    tail.draw(g2);
	  }
	  Point2D p1 = tail.getTailPosition();
	  x1 = (float) p1.getX();
	  y1 = (float) p1.getY();
	  if (vertical) {
	    xp[0] = x;
	    yp[0] = y1;
	  } else {
	    xp[0] = x1;
	    yp[0] = y;
	  }
	}
	if (i == (n - 1) && head != null && 
	    head.getWidth() < Point2D.distance(x1, y1, x2, y2)) {
	  if (g2 != null) {
	    head.draw(g2);
	  }
	  Point2D p2 = head.getTailPosition();
	  x2 = (float) p2.getX();
	  y2 = (float) p2.getY();
	}
	if (vertical) {
	  xp[i + 1] = prevx;
	  yp[i + 1] = y2;
	} else {
	  xp[i + 1] = x2;
	  yp[i + 1] = prevy;
	}
	if (i == (n - 1)) {
	  xp[n + 1] = x2;
	  yp[n + 1] = y2;
	}
	prevx = curx;
	prevy = cury;
	vertical = !vertical;
      }
    }

    GeneralPath polyline = new GeneralPath(Path2D.WIND_EVEN_ODD, n);
    polyline.moveTo(xp[0], yp[0]);
    vertical = startVertical;
    for (int i = 1; i <= n + 1; i++) {
      if (roundCorner && i <= n) {
	float x0 = xp[i], y0 = yp[i];
	x1 = x2 = x0;
	y1 = y2 = y0;
	float arch = arcHeight;
	float arcw = arcWidth;
	if (vertical) {
	  float dx = Math.abs(x0 - xp[i + 1]) / 2;
	  float dy = Math.abs(y0 - yp[i - 1]) / 2;
	  if (dx < arcWidth) {
	    arcw = dx;
	  }
	  if (dy < arcHeight) {
	    arch = dy;
	  }
	  int sy = (y0 < yp[i - 1] ? +1 : -1);
	  x1 = x0;
	  y1 = y0 + sy * arch;
	  int sx = (x0 < xp[i + 1] ? +1 : -1);
	  x2 = x0 + sx * arcw;
	  y2 = y0;
	} else {
	  float dx = Math.abs(x0 - xp[i - 1]) / 2;
	  float dy = Math.abs(y0 - yp[i + 1]) / 2;
	  if (dx < arcWidth) {
	    arcw = dx;
	  }
	  if (dy < arcHeight) {
	    arch = dy;
	  }
	  int sx = (x0 < xp[i - 1] ? +1 : -1);
	  x1 = x0 + sx * arcw;
	  y1 = y0;
	  int sy = (y0 < yp[i + 1] ? +1 : -1);
	  x2 = x0;
	  y2 = y0 + sy * arch;
	}

	if (arch > 0 && arcw > 0) {
	  polyline.lineTo(x1, y1);
	  polyline.quadTo(x0, y0, x2, y2);
	} else {
	  polyline.lineTo(x0, y0);
	}
      } else {
	polyline.lineTo(xp[i], yp[i]);
      }
      vertical = !vertical;
    }

    if (g2 != null) {
      g2.draw(polyline);
    }
    return polyline;
  }

  @Override
  public Shape getOutlineShape() {
    return makePolyLine(null);
  }

  public void adjustOrientation() {
    Point2D p = getLast();
    double dx = Math.abs(p.getX());
    double dy = Math.abs(p.getY());
    if (dx < dy) {
      startVertical = true;
    } else {
      startVertical = false;
    }
  }

  @Override
  public void normalize() {
    super.normalize();
    normalizePoints();
  }

  /*
   * Ensure the points are placed in the middle of each segment
   * Does not change the shape of the orthogonal line 
   */
  public void normalizePoints() {
    float x2, y2;
    int n = adapter.getNumberOfPoints() - 1;
    float x = adapter.getX();
    float y = adapter.getY();
    float prevx = x;
    float prevy = y;
    float curx, cury;
    boolean vertical = startVertical;
    for (int i = 0; i < n; i++) {
      Point2D p = adapter.getPoint(i); // the (i+1)-th point 
      if (p != null) {
	curx = (float) p.getX() + x;
	cury = (float) p.getY() + y;
	x2 = curx;
	y2 = cury;
	if (vertical) {
	  curx = prevx;
	  cury = y2;
	  if (i > 0) {
	    //the i-th point 
	    adapter.setPoint(i, curx, (prevy + cury) / 2);
	  }
	} else {
	  curx = x2;
	  cury = prevy;
	  if (i > 0) {
	    //the i-th point 
	    adapter.setPoint(i, (prevx + curx) / 2, cury);
	  }
	}
	prevx = curx;
	prevy = cury;
	vertical = !vertical;
      }
    }
  }

  /** move the i-th vertex by (dx, dy) */
  @Override
  public void moveVertex(int i, float dx, float dy) {
    if (debug) System.out.println("OrthogonalLineShape.moveVertext()");
    adapter.moveVertex(i, dx, dy);
    normalizePoints();
  }

  @Override
  public void setArrowPosition() {
    boolean vertical = startVertical;
    int n = adapter.getNumberOfPoints() - 1;
    if (n >= 1) {
      float x1 = adapter.getX();
      float y1 = adapter.getY();
      Point2D p = adapter.getPoint(0);
      float cx1, cy1, cx2, cy2;
      if (vertical) {
	cx1 = x1;
	cy1 = (float) p.getY() + y1;
      } else {
	cx1 = (float) p.getX() + x1;
	cy1 = y1;
      }
      p = adapter.getPoint(n - 1);
      float x2 = (float) p.getX() + x1;
      float y2 = (float) p.getY() + y1;
      if (n <= 1) {
	if (vertical) {
	  cx2 = x1;
	  cy2 = y2;
	} else {
	  cx2 = x2;
	  cy2 = y1;
	}
      } else {
	Point2D q = adapter.getPoint(n - 2);
	if (vertical ^ (n % 2 == 0)) {
	  cx2 = (float) q.getX() + x1;
	  cy2 = y2;
	} else {
	  cx2 = x2;
	  cy2 = (float) q.getY() + y1;
	}
      }
      if (head != null) {
	head.setPosition(x2, y2);
	head.setAngle((float) DrawingUtilities.calculateRotationAngle(x2, y2,
								      cx2, cy2));
      }
      if (tail != null) {
	tail.setPosition(x1, y1);
	tail.setAngle((float) DrawingUtilities.calculateRotationAngle(x1, y1,
								      cx1, cy1));
      }
    }
  }

  @Override
  public void adjustEnds(GraphNodeBase src, 
			 GraphNodeBase des, 
			 boolean adjustCtrPts) {
    if (debug) 
      System.out.println("OrthogonalLineShape.adjustEnds() adjustCtrPts=" + adjustCtrPts);

    if (adjustCtrPts) { 
      adjustMidPoint(src, des);
    }

    int n = adapter.getNumberOfPoints() - 1;
    boolean endVertical = isEndVertical();
    Point2D p1 = adapter.getFirstPoint();
    Point2D p2 = adapter.getLastPoint();
    Point2D p3 = null, p4 = null;

    float x1, y1, x2, y2;
    float cx1, cy1, cx2, cy2;
    x1 = (float) p1.getX(); 
    y1 = (float) p1.getY(); 
    x2 = (float) p2.getX(); 
    y2 = (float) p2.getY(); 

    if (n > 1) {
      // the second point 
      p3 = adapter.getPoint(0);
      
      if (startVertical) {
	cx1 = x1;
	cy1 = (float) p3.getY() + y1;
      } else {
	cx1 = (float) p3.getX() + x1;
	cy1 = y1;
      }

      // the second to the last point
      p4 = adapter.getPoint(n - 2);
      if (endVertical) { 
	cx2 = x2;
	cy2 = (float) p4.getY() + y1;
      } else {
	cx2 = (float) p4.getX() + x1;
	cy2 = y2;
      }
    } else {
      // n <= 1
      if (startVertical) {
	cx1 = x1;
	cy1 = y2;
      } else {
	cx1 = x2;
	cy1 = y1;
      }
      cx2 = cx1;
      cy2 = cy1;
    }

    Point2D r1 = new Point2DFloat(cx1, cy1);
    Point2D r2 = new Point2DFloat(cx2, cy2);

    Point2D q1, q2;
    if (src != null) {
      q1 = src.intersect(r1, 
			 tailOffset.dx * src.getShapeWidth(), 
			 tailOffset.dy * src.getShapeHeight());
    } else {
      q1 = p1;
    }

    if (des != null) {
      q2 = des.intersect(r2, 
			 headOffset.dx * des.getShapeWidth(), 
			 headOffset.dy * des.getShapeHeight());
    } else {
      q2 = p2;
    }

    if (q1 != null && q2 != null) {
      x1 = (float) q1.getX();
      y1 = (float) q1.getY(); 
      x2 = (float) q2.getX();
      y2 = (float) q2.getY();
    }      

    if (adjustCtrPts) { 
      adapter.scaleFirstLast(x1, y1, x2, y2, true, true);
      adjustNextToEndPoints(src, des);
    } else { 
      adapter.setFirstOnly(x1, y1);
      adapter.setLast(x2, y2);
    }

    normalizePoints();
    adapter.invalidateBounds();
    calculateBounds();

    if (debug) {
      System.out.println("OrthogonalLineShape.adjustEnds() n=" + (n + 1) + 
			 " vertical=" + startVertical + "/" + endVertical +
			 //" start=[" + x0 + "," + y0 + "] " + 
			 " p1=" + p1 + " p2=" + p2 + 
			 " p3=" + p3 + " p4=" + p4 + 
			 " r1=" + r1 + " r2=" + r2 + 
			 " q1=" + q1 + " q2=" + q2 + 
			 "\n  " + detailString());
    }

  }

  protected void adjustMidPoint(GraphNodeBase src, 
				GraphNodeBase des) { 
    int n = adapter.getNumberOfPoints();
    //System.out.println("OrthogonalLineShape.adjustMidPoint() n=" + n);
    if (n == 3 && src != null && des != null) { 
      //System.out.println("OrthogonalLineShape.adjustMidPoint() @1");

      float x = adapter.getX();
      float y = adapter.getY();
      boolean endVertical = isEndVertical();      
      Point2D p3 = adapter.getPoint(0);     // themid point 	
      float px = (float) p3.getX() + x;
      float py = (float) p3.getY() + y;
      float srcW = 0, srcH = 0, desW =0, desH = 0;
      float cx1 = 0, cy1 = 0, cx2 = 0, cy2 = 0;
      Point2D srcCtr = null, desCtr = null; 

      srcW = src.getShapeWidth() / 2;
      srcH = src.getShapeHeight() / 2;
      srcCtr = src.getCenter();
      cx1 = (float) srcCtr.getX();
      cy1 = (float) srcCtr.getY();
      
      desW = des.getShapeWidth() / 2;
      desH = des.getShapeHeight() / 2;
      desCtr = des.getCenter();
      cx2 = (float) desCtr.getX();
      cy2 = (float) desCtr.getY();
      
      if (startVertical && endVertical) { 
	//System.out.println("OrthogonalLineShape.adjustMidPoint() @2");
	float dist = Math.abs(cy2 - cy1);
	if (dist < (srcH + desH + nodeSep)) { 
	  float ymin, ymax;
	  if (cy1 < cy2) { 
	    ymin = cy1 - srcH - nodeSep; 
	    ymax = cy2 + desH + nodeSep; 
	  } else { 
	    ymin = cy2 - desH - nodeSep; 
	    ymax = cy1 + srcH + nodeSep; 
	  }
	  if (py > ymin && py < ymax) { 
	    if (py < (cy1 + cy2) / 2) { 
	      if (cy1 < cy2) { 
		adapter.setPoint(1, px, (cy1 - srcH - nodeSep));
	      } else { 
		adapter.setPoint(1, px, (cy2 - desH - nodeSep));
	      }
	    } else { 
	      if (cy1 > cy2) { 
		adapter.setPoint(1, px, (cy1 + srcH + nodeSep));
	      } else { 
		adapter.setPoint(1, px, (cy2 + desH + nodeSep));
	      }
	    }
	  }
	}
      } else if (!startVertical && !endVertical) { 
	//System.out.println("OrthogonalLineShape.adjustMidPoint() @3");
	float dist = Math.abs(cx2 - cx1);
	if (dist < (srcW + desW + nodeSep)) { 
	  float xmin, xmax;
	  if (cx1 < cx2) { 
	    xmin = cx1 - srcW - nodeSep; 
	    xmax = cx2 + desW + nodeSep; 
	  } else { 
	    xmin = cx2 - desW - nodeSep; 
	    xmax = cx1 + srcW + nodeSep; 
	  }
	  if (px > xmin && py < xmax) { 
	    if (px < (cx1 + cx2) / 2) { 
	      if (cx1 < cx2) { 
		adapter.setPoint(1, (cx1 - srcW - nodeSep), py);
	      } else { 
		adapter.setPoint(1, (cx2 - desW - nodeSep), py);
	      }
	    } else { 
	      if (cy1 > cy2) { 
		adapter.setPoint(1, (cx1 + srcW + nodeSep), py);
	      } else { 
		adapter.setPoint(1, (cx2 + desW + nodeSep), py);
	      }
	    }
	  }
	}
      }
    }	          
  }

  protected void adjustNextToEndPoints(GraphNodeBase src, 
				       GraphNodeBase des) { 
    int n = adapter.getNumberOfPoints();
    //System.out.println("OrthogonalLineShape.adjustNextToEndPoints() n=" + n);
    if (n > 2) { 	
      float x = adapter.getX();
      float y = adapter.getY();
      boolean endVertical = isEndVertical();      
      Point2D p3 = adapter.getPoint(0);     // the second point 	
      Point2D p4 = adapter.getPoint(n - 3); // the second to the last point  

      float srcW = 0, srcH = 0, desW =0, desH = 0;
      float cx1 = 0, cy1 = 0, cx2 = 0, cy2 = 0;
      Point2D srcCtr = null, desCtr = null; 

      if (src != null) { 
	srcW = src.getShapeWidth() / 2;
	srcH = src.getShapeHeight() / 2;
	srcCtr = src.getCenter();
	cx1 = (float) srcCtr.getX();
	cy1 = (float) srcCtr.getY();
      }
      if (des != null) { 
	desW = des.getShapeWidth() / 2;
	desH = des.getShapeHeight() / 2;
	desCtr = des.getCenter();
	cx2 = (float) desCtr.getX();
	cy2 = (float) desCtr.getY();
      }

      if (src != null) { 
	float px = (float) p3.getX() + x;
	float py = (float) p3.getY() + y;
	if (startVertical) {
	  if (py > cy1) { 
	    if (py < cy1 + srcH + nodeSep) {
	      adapter.setPoint(1, px, (cy1 + srcH + nodeSep));
	    } 
	  } else {
	    if (py > cy1 - srcH - nodeSep) {
	      adapter.setPoint(1, px, (cy1 - srcH - nodeSep));
	    } 
	  } 
	} else {
	  if (px > cx1) { 
	    if (px < cx1 + srcW + nodeSep) {
	      adapter.setPoint(1, (cx1 + srcW + nodeSep), py);
	    } 
	  } else {
	    if (px > cx1 - srcW - nodeSep) {
	      adapter.setPoint(1, (cx1 - srcW - nodeSep), py);
	    } 
	  } 	  
	}
      }

      if (des != null) { 
	float px = (float) p4.getX() + x;
	float py = (float) p4.getY() + y;
	if (endVertical) { 
	  if (py > cy2) { 
	    if (py < cy2 + desH + nodeSep) {
	      adapter.setPoint(n - 2, px, (cy2 + desH + nodeSep));
	    } 
	  } else {
	    if (py > cy2 - desH - nodeSep) {
	      adapter.setPoint(n - 2, px, (cy2 - desH - nodeSep));
	    } 
	  } 
	} else {
	  if (px > cx2) { 
	    if (px < cx2 + desW + nodeSep) {
	      adapter.setPoint(n - 2, (cx2 + desW + nodeSep), py);
	    } 
	  } else {
	    if (px > cx2 - desW - nodeSep) {
	      adapter.setPoint(n - 2, (cx2 - desW - nodeSep), py);
	    } 
	  } 	  	  
	}
      }

    }    
  }

  @Override
  public void adjustEdge() { 
    int n = adapter.getNumberOfPoints();
    if (debug) 
      System.out.println("OrthogonalLineShape.adjustEdge() n=" + n); 

    if (n == 3) { 
      // adjust the position of the middle point 
      Point2D p1 = adapter.getFirstPoint();
      Point2D p2 = adapter.getLastPoint();      
      float x1, y1, x2, y2;
      x1 = (float) p1.getX(); 
      y1 = (float) p1.getY(); 
      x2 = (float) p2.getX(); 
      y2 = (float) p2.getY(); 
      
      adapter.setPoint(1, (x1 + x2) / 2, (y1 + y2) / 2); 

      normalizePoints();
      adapter.invalidateBounds();
      calculateBounds();
    }  
    
  }

  public void scaleFirstLast(float x1, float y1, float x2, float y2,
			     boolean limit) {
    adapter.scaleFirstLast(x1, y1, x2, y2, null, limit, true);
  }

  public void swapEnds() { 
    super.swapEnds();
    if (getNumberOfPoints() % 2 == 0) { 
      startVertical = !startVertical;
    } 
  }

  @Override
  public int isInside(Point2D p) {
    if (p != null) {
      float px = (float) p.getX();
      float py = (float) p.getY();
      return isInside(px, py);
    } else {
      return PointOutside;
    }
  }

  @Override
  public int isInside(float px, float py) {
    int result = PointOutside;
    float x1 = (float) bounds.getX();
    float y1 = (float) bounds.getY();
    float x2 = (float) (bounds.getX() + bounds.getWidth());
    float y2 = (float) (bounds.getY() + bounds.getHeight());
    if ((px >= x1 - d) && (px <= x2 + d) && (py >= y1 - d) && (py <= y2 + d)) {
      // inside the rect bound by the two ends
      result = PointInBounds;

      int v = adapter.onVertex(px, py);
      if (v >= 0) {
	result = PointOnPolyShapeVertex + v;
      } else {
	int e = onEdge(px, py);
	if (e >= 0) {
	  result = PointOnPolyShapeEdge + e;
	}
      }
    }
    return result;
  }

  @Override
  protected Map<String, Object> getXMLAttributes() {
    Map<String, Object> attrs = super.getXMLAttributes();
    if (attrs == null) {
      attrs = new HashMap<String, Object>();
    }
    attrs.put("startVertical", startVertical);
    attrs.put("roundCorner", roundCorner);
    attrs.put("arch", arcHeight);
    attrs.put("arcw", arcWidth);
    return attrs;
  }

  @Override
  protected void processXMLAttributes(org.w3c.dom.Element node) {
    if (node != null) {
      super.processXMLAttributes(node);

      String s;
      s = node.getAttribute("startVertical");
      if (s != null && s.length() > 0) {
	try {
	  this.startVertical = Boolean.valueOf(s);
	} catch (NumberFormatException ex) {
	}
      }

      s = node.getAttribute("roundCorner");
      if (s != null && s.length() > 0) {
	try {
	  this.roundCorner = Boolean.valueOf(s);
	} catch (NumberFormatException ex) {
	}
      }

      s = node.getAttribute("arch");
      if (s != null && s.length() > 0) {
	try {
	  this.arcHeight = Float.parseFloat(s);
	} catch (NumberFormatException ex) {
	}
      }

      s = node.getAttribute("arcw");
      if (s != null && s.length() > 0) {
	try {
	  this.arcWidth = Float.parseFloat(s);
	} catch (NumberFormatException ex) {
	}
      }
    }
  }

  protected int onEdge(float px, float py) {
    int result = -1;
    int n = adapter.getNumberOfPoints() - 1;
    float x = adapter.getX();
    float y = adapter.getY();
    float prevx = x;
    float prevy = y;
    float curx, cury, x1, x2, y1, y2;
    boolean vertical = startVertical;
    float lastx = x;
    float lasty = y;
    for (int i = 0; i < n; i++) {
      Point2D p = adapter.getPoint(i);
      if (p != null) {
	curx = (float) p.getX() + x;
	cury = (float) p.getY() + y;
	x1 = lastx;
	y1 = lasty;
	x2 = curx;
	y2 = cury;
	if (i == 0 && 
	    tail != null && 
	    tail.getWidth() < Point2D.distance(x1, y1, x2, y2)) {
	  Point2D p1 = tail.getTailPosition();
	  if (vertical) {
	    y1 = (float) p1.getY();
	  } else {
	    x1 = (float) p1.getX();
	  }
	}
	if (i == (n - 1) && 
	    head != null && 
	    head.getWidth() < Point2D.distance(x1, y1, x2, y2)) {
	  Point2D p2 = head.getTailPosition();
	  curx = x2 = (float) p2.getX();
	  cury = y2 = (float) p2.getY();
	}

	Line2D line;
	double dist;

	if (vertical) {
	  x2 = prevx;
	} else {
	  y2 = prevy;
	}
	lastx = x2;
	lasty = y2;

	line = new Line2D.Float(x1, y1, x2, y2);
	if ((px >= Math.min(x1, x2) - 2.0d) && 
	    (px <= Math.max(x1, x2) + 2.0d) && 
	    (py >= Math.min(y1, y2) - 2.0d) && 
	    (py <= Math.max(y1, y2) + 2.0d)) {
	  dist = line.ptLineDist(px, py);
	  if (dist <= d) {
	    result = i;
	    break;
	  }
	}

	if (i == (n - 1)) {
	  x1 = lastx;
	  y1 = lasty;
	  x2 = curx;
	  y2 = cury;
	  line = new Line2D.Float(x1, y1, x2, y2);
	  if ((px >= Math.min(x1, x2) - 2.0d) && 
	      (px <= Math.max(x1, x2) + 2.0d) && 
	      (py >= Math.min(y1, y2) - 2.0d) && 
	      (py <= Math.max(y1, y2) + 2.0d)) {
	    dist = line.ptLineDist(px, py);
	    if (dist <= d) {
	      result = i + 1;
	      break;
	    }
	  }
	}

	prevx = curx;
	prevy = cury;
	vertical = !vertical;
      }
    }

    return result;
  }

  public static final float DEFAULT_ARC_HEIGHT = 8;

  public static final float DEFAULT_ARC_WIDTH = 8;

  public static final float MINIMUM_ARC_HEIGHT = 4;

  public static final float MINIMUM_ARC_WIDTH = 4;

  protected boolean startVertical = true;

  protected boolean roundCorner = false;

  protected float arcHeight = DEFAULT_ARC_HEIGHT;

  protected float arcWidth = DEFAULT_ARC_WIDTH;

  protected static final boolean debug = false;

}
